﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using MapEditor;
using System.Xml.Serialization;
using System.IO;
using System.Xml;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Audio;

namespace PFAGame
{
    public enum e_class { BOURRIN, SPECIALISTE };

    public abstract class APlayer : AObject, IXmlSerializable
    {
        public e_class playerCLass;

        // base stat
        protected float life = 100;
        protected float maxLife = 100;
        protected float moveSpeed = 5f;
        protected float attackSpeed = 0.5f;
        protected float dammage = 34f;
        protected float criticalChance = 10f;
        protected float resist = 0f;
        protected float range = 1f;
        protected float angleAttack = 135;
        protected float jumpStrengh = 13;

        // template
        private int level = 1;
        protected int exp = 0;
        private int healthTemplate = 0;
        private int damageTemplate = 0;
        private int attackSpeedTemplate = 0;
        private int moveSpeedTemplate = 0;
        private int resistTemplate = 0;
        protected int unusedPoint = 0;

        // buff
        protected float dammageBuffMultiplicator = 1f;
        protected float attackSpeedBuffMultiplicator = 1f;
        private float buffDuration = -1f;
        private float resistBuffAdd = 0f;

        // const
        public const float aggro = 1000;
        public const float criticalMultiplicator = 2f;

        //getter
        public float Life
        {
            get
            {
                return life;
            }
            set
            {
                this.life = value;
            }
        }
        public int Level
        {
            get
            {
                return level;
            }
            set
            {
                this.level = value;
            }
        }
        public float MoveSpeed      { get { return moveSpeed; } }
        public float AttackSpeed    { get { return attackSpeed; } }
        public float Dammage        { get { return dammage; } }
        public float CriticalChance { get { return criticalChance; } }
        public float Resist         { get { return resist; } }
        public float JumpStrengh    { get { return resist; } }
        public float MaxLife        { get { return maxLife; } }
        public float Exp            { get { return exp; } }

        public int UnusedTemplate       { get { return unusedPoint; } }
        public int HealthTemplate       { get { return healthTemplate; } }
        public int DamageTemplate       { get { return damageTemplate; } }
        public int AttackSpeedTemplate  { get { return attackSpeedTemplate; } }
        public int MoveSpeedTemplate    { get { return moveSpeedTemplate; } }
        public int ResistTemplate       { get { return resistTemplate; } }

        public  float AttackSpeedBuffMultiplicator
        {
            get { return attackSpeedBuffMultiplicator; }
            set { attackSpeedBuffMultiplicator = value; }
        }
        public float DammageBuffMultiplicator
        {
            get { return dammageBuffMultiplicator; }
            set { dammageBuffMultiplicator = value; }
        }
        public float ResistBuffAdd
        {
            get { return resistBuffAdd; }
            set { resistBuffAdd = value; }
        }
        public float BuffDuration
        {
            get { return buffDuration; }
            set { buffDuration = value; }
        }

        protected PlayerIndex? playerIndex;

        protected InputAction fire1;
        protected InputAction fire2;

        protected InputAction jump;

        protected InputAction moveUp;
        protected InputAction moveDown;
        protected InputAction moveLeft;
        protected InputAction moveRight;

        protected InputAction directionUp;
        protected InputAction directionDown;
        protected InputAction directionLeft;
        protected InputAction directionRight;

        protected InputAction cheat;

        private Vector2 tmpPosition = new Vector2();

        private List<Vector2> positionList = new List<Vector2>();
        private const int savedPositionNumber = 10;
        private const float timeBetweenPosition = 0.1f;
        private float time = 0;

        // déplacement platform
        private float verticalVelocity = 0;
        private bool isGrounded = false;
        public float VerticalVelocity   { get { return verticalVelocity; } }
        public bool IsGrounded          { get { return isGrounded; } }

        public int currentMap;

        // gestion des animation
        protected Animation moveRightAnim = new Animation();
        protected Animation moveUpAnim = new Animation();
        protected Animation moveDownAnim = new Animation();
        protected Animation moveLeftAnim = new Animation();
        protected Animation attackUpAnim = new Animation();
        protected Animation attackRightAnim = new Animation();
        protected Animation attackDownAnim = new Animation();
        protected Animation attackLeftAnim = new Animation();
        protected bool isAttacking = false;
        protected bool IsMoving
        { 
            set 
            {
                moveRightAnim.Active = value;
                moveUpAnim.Active = value;
                moveDownAnim.Active = value;
                moveLeftAnim.Active = value;
            } 
        }

        static SoundEffect sound;
        SoundEffectInstance soundInstance;

        public override void init()
        {
            base.init();
            playerIndex = null;

            
            // attaque
            fire1 = new InputAction(new Buttons[] { Buttons.RightTrigger },
                                    new Keys[] { Keys.LeftControl },
                                    new MouseButtons[] { MouseButtons.Left },
                                    false);

            fire2 = new InputAction(new Buttons[] { Buttons.LeftTrigger },
                                    new Keys[] { Keys.LeftAlt },
                                    new MouseButtons[] { MouseButtons.Right },
                                    false);

            // deplacement
            jump = new InputAction(new Buttons[] { Buttons.A },
                        new Keys[] { Keys.Space },
                        null,
                        false);

            moveUp = new InputAction(new Buttons[] { Buttons.LeftThumbstickUp },
                                    new Keys[] { Keys.Z, Keys.W },
                                    null,
                                    false);

            moveDown = new InputAction(new Buttons[] { Buttons.LeftThumbstickDown },
                                    new Keys[] { Keys.S },
                                    null,
                                    false);

            moveLeft = new InputAction(new Buttons[] { Buttons.LeftThumbstickLeft },
                                    new Keys[] { Keys.Q, Keys.A },
                                    null,
                                    false);

            moveRight = new InputAction(new Buttons[] { Buttons.LeftThumbstickRight },
                                    new Keys[] { Keys.D },
                                    null,
                                    false);

            cheat = new InputAction(new Buttons[] { Buttons.B },
                                    new Keys[] { Keys.F5 },
                                    null,
                                    false);

            // direction
            directionUp = new InputAction(new Buttons[] { Buttons.RightThumbstickUp },
                                            new Keys[] { Keys.Up },
                                            null,
                                            false);

            directionDown = new InputAction(new Buttons[] { Buttons.RightThumbstickDown },
                                    new Keys[] { Keys.Down },
                                    null,
                                    false);

            directionLeft = new InputAction(new Buttons[] { Buttons.RightThumbstickLeft },
                                    new Keys[] { Keys.Left },
                                    null,
                                    false);

            directionRight = new InputAction(new Buttons[] { Buttons.RightThumbstickRight },
                                    new Keys[] { Keys.Right },
                                    null,
                                    false);

            //init calcul de direction
            for (int i = 0; i < savedPositionNumber; i++)
                positionList.Add(new Vector2(position.X, position.Y));

            //
            if (sound == null)
                sound = ScreenManager.Singleton.Game.Content.Load<SoundEffect>("Sound_effects/jump");
            soundInstance = sound.CreateInstance();
            soundInstance.Volume = 0.2f;
            soundInstance.IsLooped = false;
        }

        public virtual void HandleInput(GameTime gameTime, InputState input)
        {
            PlayerIndex tmpPlayerIndex;

            // applique le deplacement
            Vector2 move = new Vector2(0, 0);
            if (moveUp.Evaluate(input, playerIndex, out tmpPlayerIndex))
                move.Y--;
            if (moveDown.Evaluate(input, playerIndex, out tmpPlayerIndex))
                move.Y++;
            if (moveRight.Evaluate(input, playerIndex, out tmpPlayerIndex))
                move.X++;
            if (moveLeft.Evaluate(input, playerIndex, out tmpPlayerIndex))
                move.X--;
            if (isGrounded && jump.Evaluate(input, playerIndex, out tmpPlayerIndex))
            {
                verticalVelocity += jumpStrengh;
                soundInstance.Play();
            }
            if (move.X != 0 || move.Y != 0)
            {
                move.Normalize();
                IsMoving = true;
                //test la colision indépendament sur chaqu'un des axes pour permettre de glisser sur les cases bloquantes
                if (GameplayScreen.Singleton.CurrentType == 1) //on ne peut pas monter en vue de platform
                {
                    tmpPosition = position + move * moveSpeed * ((float)gameTime.ElapsedGameTime.Ticks / 10000000) * (float)(SphereCastBool(tmpPosition, 0.1f, GameplayScreen.Singleton.MobList) ? 0.3 : 1);
                    tmpPosition.X = position.X;

                    if (!GameplayScreen.Singleton.MapP.checkCollision(tmpPosition))
                        position = tmpPosition;
                }

                tmpPosition = position + move * moveSpeed * ((float)gameTime.ElapsedGameTime.Ticks / 10000000) * (float)(SphereCastBool(tmpPosition, 0.1f, GameplayScreen.Singleton.MobList) ? 0.3 : 1);
                tmpPosition.Y = position.Y;
                if (!GameplayScreen.Singleton.MapP.checkCollision(tmpPosition))
                    position = tmpPosition;
            }
            else
                IsMoving = false;
            // applique la rotation
            //TODO: gérer le cas où un joueur qui n'a rien compris à la vie, veut jouer au pad sur PC
            #if WINDOWS
            //direction souris
            Vector2I mousePositionOnScreen = Cursor.Singleton.Position;
            Camera camera = GameplayScreen.Singleton.CameraP;
            Vector2 mousePositionWorldSpace = new Vector2((float)(mousePositionOnScreen.x - camera.OffsetMiddleScreen.X) / camera.ZoomLevel + camera.Position.X,
                                                          (float)(mousePositionOnScreen.y - camera.OffsetMiddleScreen.Y) / camera.ZoomLevel + camera.Position.Y);
            orientation = position - mousePositionWorldSpace;
            orientation.Normalize();
            #else
            //direction au pad
            orientation.X = 0; orientation.Y = 1;
            if (directionUp.Evaluate(input, playerIndex, out tmpPlayerIndex))
                orientation.Y = input.CurrentGamePadStates[0].ThumbSticks.Right.Y;
            if (directionDown.Evaluate(input, playerIndex, out tmpPlayerIndex))
                orientation.Y = input.CurrentGamePadStates[0].ThumbSticks.Right.Y;
            if (directionRight.Evaluate(input, playerIndex, out tmpPlayerIndex))
                orientation.X = -input.CurrentGamePadStates[0].ThumbSticks.Right.X;
            if (directionLeft.Evaluate(input, playerIndex, out tmpPlayerIndex))
                orientation.X = -input.CurrentGamePadStates[0].ThumbSticks.Right.X;
            if (orientation.X != 0 || orientation.Y != 0)
                orientation.Normalize();
            #endif

            if (cheat.Evaluate(input, playerIndex, out tmpPlayerIndex))
            {
                if (resist > 9999)
                    resist = 0;
                else
                    resist = 100000;
            }
        }

        public override void update(GameTime gameTime)
        {
            base.update(gameTime);

            // gestion des buffs
            if (buffDuration > 0)
            {
                buffDuration -= (float)gameTime.ElapsedGameTime.Ticks / 10000000;
                // fin du buff
                if (buffDuration < 0)
                {
                    attackSpeedBuffMultiplicator = 1;
                    dammageBuffMultiplicator = 1;
                    resistBuffAdd = 0;
                }
            }

            // applique la gravité en vue de coté
            if (GameplayScreen.Singleton.CurrentType != 1)
            {
                verticalVelocity += GameplayScreen.gravity * (float)gameTime.ElapsedGameTime.Ticks / 10000000;
                tmpPosition = position;
                tmpPosition.Y -= verticalVelocity * ((float)gameTime.ElapsedGameTime.Ticks / 10000000);
                if (!GameplayScreen.Singleton.MapP.checkCollision(tmpPosition))
                {
                    position = tmpPosition;
                    isGrounded = false;
                }
                else
                {
                    verticalVelocity = 0;
                    isGrounded = true;
                }
            }

            // calcul de la direction moyenne du joueur, utilisé dans les calcul de trajectoire des mobs
            time += (float)gameTime.ElapsedGameTime.Ticks / 10000000;
            if (time < timeBetweenPosition)
                return;
            time = 0;
            positionList.Add(position);
            positionList.RemoveAt(0);
            direction.X = 0;
            direction.Y = 0;
            foreach (Vector2 pos in positionList)
                direction += pos;
            direction /= positionList.Count;
            direction -= position;
            direction *= 2.5f;

            // update anim
            if (!isAttacking)
            {
                moveRightAnim.Update(gameTime);
                moveUpAnim.Update(gameTime);
                moveDownAnim.Update(gameTime);
                moveLeftAnim.Update(gameTime);
            }
            else
            {
                attackUpAnim.Update(gameTime);
                attackRightAnim.Update(gameTime);
                attackDownAnim.Update(gameTime);
                attackLeftAnim.Update(gameTime);
            }
        }

        public void receiveDammage(float dammage)
        {
            float dammageResult = dammage - (resist + resistBuffAdd);
            if (dammageResult > 0)
                life -= dammageResult;
        }

        public void receiveHeal(float heal)
        {
            life += heal;
            if (life > maxLife)
                life = maxLife;
        }

        public void receiveExp(int value)
        {
            exp += value;

            //lvl up
            if (exp >= 100 * level)
            {
                exp -= 100 * level;
                level++;
                unusedPoint++;
                save();
            }
        }

        /// <summary>
        /// reset les atributs du joueur à leur valeur initial
        /// </summary>
        public virtual void respawn()
        {
            life = maxLife;
            verticalVelocity = 0;
            buffDuration = -1;
            attackSpeedBuffMultiplicator = 1;
            dammageBuffMultiplicator = 1;
        }

        abstract internal string getClass();

        public void save()
        {
        #if WINDOWS
            XmlSerializer xs;
            if (playerCLass == e_class.BOURRIN)
                xs = new XmlSerializer(typeof(Bourrin));
            else
                xs = new XmlSerializer(typeof(Specialiste));

            using (StreamWriter wr = new StreamWriter("save_" + getClass() + "_PLAYERNAME" + ".xml"))
            {
                xs.Serialize(wr, this);
            }
        #endif
        }

        static public APlayer load(e_class playerClass, out int currentMap)
        {
            string className = playerClass == e_class.BOURRIN ? "Bourrin" : "Spécialiste";
            XmlSerializer xs;
            if (playerClass == e_class.BOURRIN)
                xs = new XmlSerializer(typeof(Bourrin));
            else
                xs = new XmlSerializer(typeof(Specialiste));
            APlayer player;
            try
            {
                using (StreamReader rd = new StreamReader("save_" + className + "_PLAYERNAME" + ".xml"))
                {
                    player = (APlayer)xs.Deserialize(rd);
                }
                currentMap = player.currentMap;
            }
            catch (FileNotFoundException)
            {
                if (playerClass == e_class.BOURRIN)
                    player = new Bourrin();
                else
                    player = new Specialiste();
                currentMap = 0;
            }
            return player;
        }

        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(System.Xml.XmlReader reader)
        {
            int deth = reader.Depth;
            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.Name == "level")
                    {
                        if (reader.Read())
                        {
                            if (reader.NodeType == XmlNodeType.Text)
                                level = int.Parse(reader.Value);
                        }
                    }
                    else if (reader.Name == "exp")
                    {
                        if (reader.Read())
                        {
                            if (reader.NodeType == XmlNodeType.Text)
                                exp = int.Parse(reader.Value);
                        }
                    }
                    else if (reader.Name == "currentMap")
                    {
                        if (reader.Read())
                        {
                            if (reader.NodeType == XmlNodeType.Text)
                                currentMap = int.Parse(reader.Value);
                        }
                    }
                    else if (reader.Name == "template")
                        while (!(reader.LocalName == "template" && reader.NodeType == XmlNodeType.EndElement))
                        {
                            if (reader.Read() && reader.NodeType == XmlNodeType.Element)
                            {
                                if (reader.Name == "health")
                                {
                                    if (reader.Read())
                                    {
                                        if (reader.NodeType == XmlNodeType.Text)
                                            healthTemplate = Convert.ToInt32(reader.Value);
                                    }
                                }
                                else if (reader.Name == "damage")
                                {
                                    if (reader.Read())
                                    {
                                        if (reader.NodeType == XmlNodeType.Text)
                                            damageTemplate = Convert.ToInt32(reader.Value);
                                    }
                                }
                                else if (reader.Name == "attackSpeed")
                                {
                                    if (reader.Read())
                                    {
                                        if (reader.NodeType == XmlNodeType.Text)
                                            attackSpeedTemplate = Convert.ToInt32(reader.Value);
                                    }
                                }
                                else if (reader.Name == "moveSpeed")
                                {
                                    if (reader.Read())
                                    {
                                        if (reader.NodeType == XmlNodeType.Text)
                                            moveSpeedTemplate = Convert.ToInt32(reader.Value);
                                    }
                                }
                                else if (reader.Name == "resist")
                                {
                                    if (reader.Read())
                                    {
                                        if (reader.NodeType == XmlNodeType.Text)
                                            resistTemplate = Convert.ToInt32(reader.Value);
                                    }
                                }
                                else if (reader.Name == "unusedPoint")
                                {
                                    if (reader.Read())
                                    {
                                        if (reader.NodeType == XmlNodeType.Text)
                                            unusedPoint = Convert.ToInt32(reader.Value);
                                    }
                                }
                            }
                            if (reader.EOF == true)
                                break;
                        }
                }
                if (deth == reader.Depth)
                {
                    reader.Read();
                    break;
                }
            }

            // initialisation des caractéristiques
            for (int i = 0; i < healthTemplate; i++)
                upHealth(true);
            for (int i = 0; i < damageTemplate; i++)
                upDamage(true);
            for (int i = 0; i < attackSpeedTemplate; i++)
                upAttackSpeed(true);
            for (int i = 0; i < moveSpeedTemplate; i++)
                upMoveSpeed(true);
            for (int i = 0; i < resistTemplate; i++)
                upResist(true);
        }

        public void WriteXml(System.Xml.XmlWriter writer)
        {
            writer.WriteElementString("class", getClass());
            writer.WriteElementString("level", level.ToString());
            writer.WriteElementString("exp", exp.ToString());
            writer.WriteElementString("currentMap", GameplayScreen.Singleton.CurrentLevel.ToString());
            writer.WriteStartElement("template");
            writer.WriteElementString("health", healthTemplate.ToString());
            writer.WriteElementString("damage", damageTemplate.ToString());
            writer.WriteElementString("attackSpeed", attackSpeedTemplate.ToString());
            writer.WriteElementString("moveSpeed", moveSpeedTemplate.ToString());
            writer.WriteElementString("resist", resistTemplate.ToString());
            writer.WriteElementString("unusedPoint", unusedPoint.ToString());
            writer.WriteEndElement();
        }

        public virtual bool upHealth(bool init) 
        {
            if (unusedPoint <= 0)
                return false;
            unusedPoint--;
            healthTemplate++;
            return true;
        }

        public virtual bool upDamage(bool init) 
        {
            if (unusedPoint <= 0)
                return false;
            unusedPoint--;
            damageTemplate++;
            return true;
        }

        public virtual bool upAttackSpeed(bool init) 
        {
            if (unusedPoint <= 0)
                return false;
            unusedPoint--;
            attackSpeedTemplate++;
            return true;
        }

        public virtual bool upMoveSpeed(bool init)
        {
            if (unusedPoint <= 0)
                return false;
            unusedPoint--;
            moveSpeedTemplate++;
            return true;
        }

        public virtual bool upResist(bool init) 
        {
            if (unusedPoint <= 0)
                return false;
            unusedPoint--;
            resistTemplate++;
            return true;
        }

        public override void draw(GameTime gameTime, Camera camera)
        {
            // la base calcul le rectangle d'affichage
            base.draw(gameTime, camera);

            Animation anim;
            //choix de la direction
            if (Math.Abs(orientation.X) > Math.Abs(orientation.Y) || GameplayScreen.Singleton.CurrentType != 1)
            {
                // ici droite ou gauche
                if (!isAttacking)
                    anim = orientation.X > 0 ? moveRightAnim : moveLeftAnim;
                else
                    anim = orientation.X > 0 ? attackRightAnim : attackLeftAnim;
            }
            else
            {
                // ici haut ou bas
                if (!isAttacking)
                    anim = orientation.Y > 0 ? moveUpAnim : moveDownAnim;
                else
                    anim = orientation.Y > 0 ? attackUpAnim : attackDownAnim;
            }

            // on multiplie par 2 la taille du joueur:
            displayRectangle.X -= camera.ZoomLevel / 3;
            displayRectangle.Y -= camera.ZoomLevel / 3;
            displayRectangle.Width = displayRectangle.Width * 3 / 2;
            displayRectangle.Height = displayRectangle.Height * 3 / 2;

            anim.Draw(ScreenManager.Singleton.SpriteBatch, displayRectangle, Color.White);
        }

        #region static

        static public APlayer getClosestPlayer(Vector2 objectPosition, out float minDistanceSquared)
        {
            minDistanceSquared = float.MaxValue;
            APlayer closestPlayer = GameplayScreen.Singleton.Players.First();
            foreach (APlayer player in GameplayScreen.Singleton.Players)
            {
                Vector2 distance = objectPosition - player.Position;
                float distanceSquared = distance.LengthSquared();
                if (distanceSquared < minDistanceSquared)
                {
                    minDistanceSquared = distanceSquared;
                    closestPlayer = player;
                }
            }

            // network
            if (GameplayScreen.Singleton.NetworkSessionP != null && GameplayScreen.Singleton.NetworkSessionP.IsHost == true)
            {
                foreach (APlayer player in GameplayScreen.Singleton.DicoPlayer.Values)
                {
                    Vector2 distance = objectPosition - player.Position;
                    float distanceSquared = distance.LengthSquared();
                    if (distanceSquared < minDistanceSquared)
                    {
                        minDistanceSquared = distanceSquared;
                        closestPlayer = player;
                    }
                }
            }

            return closestPlayer;
        }

        protected Random random = new Random();

        #endregion
    }
}
